#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <types.h>
#include <utils.h>
#include <unistd.h>
#include <syserr.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/epoll.h>
#include "object.h"
#include "device.h"
#include "dlist.h"
#include "printk.h"

#define DEV_CHK_ACTIVE(dev)                                     \
do{                                                             \
    if (!ev__is_active(dev)) {                                  \
        DEV_PE("<%s>: not active, return!\n", dev_name(dev));   \
        return -EHOSTDOWN;                                      \
    }                                                           \
    assert(dev_fd(dev) > 0);                                    \
} while(0)

void dev_set_faynsc(int fd, int on)
{
    u32 flags;

    fcntl(fd, F_SETOWN, getpid());
    flags = fcntl(fd, F_GETFL);
    if (on)
        flags |= FASYNC;
    else
        flags &= ~FASYNC;
    fcntl(fd, F_SETFL, flags);
}

/*
 * device add, del, find
 */
int device_register(device_t *dev, const char *name, u32 flag)
{
    dev->flag = flag;
    dev_fd(dev) = -1;
    return obj_add(to_obj(dev), OBJ_DEV, name);
}
void device_unregister(device_t *dev)
{
    if (ev__is_active(dev))
        device_close(dev);

    obj_del(to_obj(dev));
}
device_t *device_find(const char *name)
{
    return (device_t *)obj_find_with_type(OBJ_DEV, name);
}
int device_active(device_t *dev)
{
    return ev_is_active(dev);
}
void device_restart(device_t *dev)
{
    device_reset(dev);
    device_open(dev, dev->loop, dev->oflag);
}

static void device_autoopen_tmr_cb(void *data)
{
    device_restart((device_t *)data);
}
static void device_init(device_t *dev, ev_loop_t *loop, u32 flag)
{
    dev->loop   = loop;
    dev->master = dev;
    dev->proxy  = NULL;
    dev->delayed_error = 0;
    dev_status(dev) = DEVICE_STATUS_IDLE;
    INIT_LIST_HEAD(&dev->subdev_entry);
    soft_timer_add(loop, &dev->open_tmr, "tmr_open", device_autoopen_tmr_cb, dev);
}
int device_open(device_t *dev, ev_loop_t *loop, u32 flag)
{
    int ret = 0;
    const struct device_ops *ops = dev->ops;

    assert(dev && ops);

    DEV_PD("<%s>: open opt...\n", dev_name(dev));
    /* already open.
     * if you do want to reopen me, please close me first.
     */
    if (ev__is_active(dev))
    {
        DEV_PE("<%s>: device alread active\n", dev_name(dev));
        return -EALREADY;
    }

    dev->oflag = flag;
    /* do init procedure, e.g., add a soft timer or init self private data.
     */
    if (!to_obj(dev)->init)
    {
        DEV_PD("<%s>: device init...\n", dev_name(dev));

        device_init(dev, loop, flag);
        if (ops->init)
        {
            ret = ops->init(dev);
            if (ret < 0) goto err;
        }
        to_obj(dev)->init = 1;
    }

    /* do private open...
     */
    if (dev_fd(dev) < 0)
    {
        DEV_PD("<%s>: device private open...\n", dev_name(dev));
        ret = ops->open(dev);
        if (ret < 0) goto err;

        if (flag & DEV_OFLAG_SIGNAL)
            dev_set_faynsc(ret, 1);
        dev_fd(dev) = ret;
        memset(&dev->dstats, 0, sizeof(dev->dstats));
    }

    ev__handle_start(dev);

    DEV_PI("<%s>: device open done!\n", dev_name(dev));
    return 0;

err:
    if (dev->autoopen)
    {
        soft_timer_start(&dev->open_tmr, HZ, 0);
        return -EINPROGRESS;
    }
    DEV_PE("<%s>: device open fault, ret[%d]!\n", dev_name(dev), ret);
    device_close(dev);
    return -EIO;
}

static void device_close_subdev(device_t *dev)
{
    device_t *subdev;

    while (!list_empty(&dev->subdev_entry))
    {
        subdev = list_first_entry(&dev->subdev_entry, device_t, subdev_entry);

        list_del(&subdev->subdev_entry);
        device_close(subdev);
    }
}

void device_reset(device_t *dev)
{
    const struct device_ops *ops = dev->ops;

    /* close subdev first if we have.
     */
    if (dev_is_master(dev))
        device_close_subdev(dev);

    if (ops && ops->reset)
        ops->reset(dev);

    int fd = dev_fd(dev);

    dev->delayed_error = 0;
    if (fd > 0)
    {
        if (dev->oflag & DEV_OFLAG_SIGNAL)
            dev_set_faynsc(fd, 0);

        /* stop all epoll event watcher.
         */
        ev__io_stop(dev_loop(dev), &dev->io, EVT_ALL);

        /* close file descriptor.
         */
        close(fd);
        dev_fd(dev) = -1;
    }

    dev->status = DEVICE_STATUS_IDLE;
    ev__handle_stop(dev);
}

static void device_release(device_t *dev)
{
    soft_timer_del(&dev->open_tmr);
    to_obj(dev)->init = 0;
}

void device_close(device_t *dev)
{
    const struct device_ops *ops = dev->ops;

    /* close subdev first if we have.
     */
    if (dev_is_master(dev))
        device_close_subdev(dev);

    device_reset(dev);

    if (ops && ops->release)
        ops->release(dev);
    device_release(dev);
}

void device_io(ev_io_t *io, u32 e)
{
    device_t *dev = container_of(io, device_t, io);

    if (e & (EPOLLIN | EPOLLERR | EPOLLHUP | EPOLLRDHUP))
    {
        DEV_PD("<%s>: event[%08x]\n", dev_name(dev), e);

        if (dev->ops && dev->ops->on_poll_in)
            dev->ops->on_poll_in(dev);
        else if (dev->usr_cb && dev->usr_cb->ev_cb)
            dev->usr_cb->ev_cb(dev, DE_RCV, NULL);
    }
    else if (e & (EPOLLOUT))
    {
        DEV_PD("<%s>: event[%08x]\n", dev_name(dev), e);

        if (dev->ops && dev->ops->on_poll_out)
            dev->ops->on_poll_out(dev);
    }
}

ssize_t device_peek(device_t *dev, int pos, void *buffer, size_t size)
{
    int ret;

    if (dev->proxy) dev = dev->proxy;

    DEV_CHK_ACTIVE(dev);

    if (dev->flag & DEV_FLAG_RDING)
        return -EBUSY;

    dev->flag |= DEV_FLAG_RDING;

    if (dev->ops && dev->ops->peek)
        ret = dev->ops->peek(dev, buffer, size);
    else
        ret = -ENOSYS;

    dev->flag &= ~DEV_FLAG_RDING;
    return ret;
}

ssize_t device_read(device_t *dev, int pos, void *buffer, size_t size)
{
    int ret;

    DEV_PD("<%s>: %s...\n", dev_name(dev), __func__);
    if (dev->proxy)
    {
        DEV_PD("<%s>: %s use proxy[%s] instead...\n",
               dev_name(dev), __func__, dev_name(dev->proxy));
        dev = dev->proxy;
    }

    DEV_CHK_ACTIVE(dev);

    if (dev->flag & DEV_FLAG_RDING)
        return -EBUSY;

    dev->flag |= DEV_FLAG_RDING;

    if (dev->ops && dev->ops->read)
        ret = dev->ops->read(dev, buffer, size);
    else
        ret = read(dev_fd(dev), buffer, size);

    dev->flag &= ~DEV_FLAG_RDING;
    return ret;
}

ssize_t device_write(device_t *dev, int pos, const void *buffer, size_t size)
{
    DEV_PD("<%s>: %s...\n", dev_name(dev), __func__);

    if (dev->proxy)
    {
        DEV_PD("<%s>: %s use proxy[%s] instead...\n",
               dev_name(dev), __func__, dev_name(dev->proxy));
        dev = dev->proxy;
    }

    DEV_CHK_ACTIVE(dev);

    if (dev->flag & DEV_FLAG_BLOCK)
        lseek(dev_fd(dev), pos, SEEK_SET);

    if (dev->ops && dev->ops->write)
        return dev->ops->write(dev, buffer, size);

    return write(dev_fd(dev), buffer, size);
}

int device_ctrl(device_t *dev, u32 cmd, void *arg)
{
    DEV_PD("<%s>: %s cmd[%d]\n", dev_name(dev), __func__, cmd);

    switch (cmd)
    {
    case DEV_CTRL_CMD_RESTART:
    {
        long delay = (long)arg;
        soft_timer_start(&dev->open_tmr, delay, 0);
        break;
    }
    case DEV_CTRL_CMD_GET_STATUS:
        *(u32 *)arg = dev_status(dev);
        break;
    default:
        if (dev->ops && dev->ops->ctrl)
            return dev->ops->ctrl(dev, cmd, arg);
        break;
    }
    return -ENOSYS;
}

void device_set_cb(device_t *dev, const struct usr_cb_ops *cb)
{
    dev->usr_cb = cb;
}

int device_send_start(device_t *dev)
{
    DEV_PD("<%s>: %s...\n", dev_name(dev), __func__);

    if (dev->proxy)
    {
        DEV_PD("<%s>: %s use proxy[%s] instead...\n",
               dev_name(dev), __func__, dev_name(dev->proxy));
        dev = dev->proxy;
    }

    DEV_CHK_ACTIVE(dev);

    ev__io_start(dev_loop(dev), &dev->io, EPOLLOUT);
    return 0;
}
int device_send_stop(device_t *dev)
{
    DEV_PD("<%s>: %s...\n", dev_name(dev), __func__);

    if (dev->proxy)
    {
        DEV_PD("<%s>: %s use proxy[%s] instead...\n",
               dev_name(dev), __func__, dev_name(dev->proxy));
        dev = dev->proxy;
    }

    DEV_CHK_ACTIVE(dev);

    ev__io_stop(dev_loop(dev), &dev->io, EPOLLOUT);
    return 0;
}

void device_dump(void)
{
    object_t *iter;

    printk("|==================================================|\r\n");
    printk("|               name|               loop|  fd|oflag|\r\n");
    printk("|==================================================|\r\n");

    list_for_each_entry(iter, obj_head(OBJ_DEV), entry)
    {
        device_t *dev = (device_t *)iter;

        printk("|%19s|%19s|%4d| %4x|\r\n",
               iter->name, dev_loop(dev) ? dev_loop(dev)->obj.name : "N/A",
               dev_fd(dev), dev->oflag);
    }
    printk("|==================================================|\r\n");
}
